
#include "validator.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <assert.h>

typedef struct cconf_intrange_s {
    int lo;
    int hi;
} cconf_intrange_t;

typedef struct cconf_doublerange_s {
    double lo;
    double hi;
} cconf_doublerange_t;

static int _validate_intrange(char ** out, char * value, void * arg)
{
    char * endp;
    int v = strtol(value, &endp, 0);
    
    if (value[0] == '\0' || endp[0] != '\0')
    {
		asprintf(out, "%s", "incorrect type. expected integer");
        return CCONF_VALIDATE_ERROR_TYPE;
	}
    
    if (errno == ERANGE)
    {
		asprintf(out, "%s", "out of numeric type range");
        return CCONF_VALIDATE_ERROR_NUMRANGE;
	}
    
    cconf_intrange_t * range = arg;
    
    if (range && (v < range->lo || v > range->hi))
    {
		asprintf(out, "out of specified range [%d, %d]", range->lo, range->hi);
        return CCONF_VALIDATE_ERROR_RANGE;
	}
    
    return CCONF_VALIDATE_ERROR_NO_ERROR;
}

static int _validate_doublerange(char ** out, char * value, void * arg)
{
    char * endp;
    double v = strtod(value, &endp);
    
    if (value[0] == '\0' || endp[0] != '\0')
    {
		asprintf(out, "%s", "incorrect type. expected a real number");
        return CCONF_VALIDATE_ERROR_TYPE;
	}
    
    if (errno == ERANGE)
    {
		asprintf(out, "%s", "out of numeric type range");
        return CCONF_VALIDATE_ERROR_NUMRANGE;
	}
    
    cconf_doublerange_t * range = arg;
    
    if (range && (v < range->lo || v > range->hi))
    {
		asprintf(out, "out of specified range [%f, %f]", range->lo, range->hi);
        return CCONF_VALIDATE_ERROR_RANGE;
	}
    
    return CCONF_VALIDATE_ERROR_NO_ERROR;
}

static int _validate_string(char ** out, char * value, void * arg)
{
    
    return 0;
}

cconf_validator_t * cconf_validator_new_intrange(int low, int hi)
{
    cconf_validator_t * v = calloc(1, sizeof(cconf_validator_t));
    
    cconf_intrange_t * range = calloc(1, sizeof(cconf_intrange_t));
    range->lo = low; range->hi = hi;
    
    asprintf(&v->docstring, "integer, range=[%d, %d]", low, hi);
    
    v->arg = range;
    v->arg_free_fcn = &free;
    v->validate_fcn = &_validate_intrange;
    
    return v;
}

cconf_validator_t * cconf_validator_new_doublerange(double low, double hi)
{
    cconf_validator_t * v = calloc(1, sizeof(cconf_validator_t));
    
    cconf_doublerange_t * range = calloc(1, sizeof(cconf_doublerange_t));
    range->lo = low; range->hi = hi;
    
    asprintf(&v->docstring, "real number (double), range=[%f, %f]", low, hi);
    
    v->arg = range;
    v->arg_free_fcn=&free;
    v->validate_fcn = &_validate_doublerange;
    
    return v;
}

cconf_validator_t * cconf_validator_new_int()
{
    cconf_validator_t * v = calloc(1, sizeof(cconf_validator_t));
    
    asprintf(&v->docstring, "integer");
    
    v->arg = NULL;
    v->validate_fcn = &_validate_intrange;
    
    return v;
}

cconf_validator_t * cconf_validator_new_double()
{
    cconf_validator_t * v = calloc(1, sizeof(cconf_validator_t));
    
    asprintf(&v->docstring, "real number (double)");
    
    v->arg = NULL;
    v->validate_fcn = &_validate_doublerange;
    
    return v;
}

char ** str_to_list(char * str, char * delim)
{
	char ** list = NULL;
    char * string=str, * save, * token;
    int k;
        
    for (k = 0; ; k++, string = NULL)
    {
		token = strtok_r(string, delim, &save);
		
		if (token == NULL)
			break;
		
		list = realloc(list, (k+2) * sizeof(char *));
		list[k] = strdup(token);
		list[k+1] = NULL;
	}
	
	return list;
}

void strlist_free(char ** arg)
{
	char ** list = arg;
	int k = 0;
	
	for (k = 0; list[k] != NULL; k++)
		free(list[k]);
	
	free(list);
}

static int _validate_strlist(char ** out, char * value, void * arg)
{
    char ** alist = arg;
    char * val = strdup(value);
    char ** vlist = str_to_list(val, ",");
    free(val);
    assert(vlist != NULL && alist != NULL);
    int j, k, ok;
    
    j = 0;
    
    while (vlist[j] != NULL)
    {
		ok = 0; k = 0;
		
		while ((alist[k] != NULL) && !ok)
		{		
			ok |= !strcmp(alist[k], vlist[j]);
			k++;
		}
		
		j++;
	}
	
	strlist_free(vlist);
	
	if (!ok)
	{
		asprintf(out, "\"%s\" is not a member of the set", value);
        return CCONF_VALIDATE_ERROR_RANGE;
	}
    
    return CCONF_VALIDATE_ERROR_NO_ERROR;
}

cconf_validator_t * cconf_validator_new_strlist(char * str)
{
	char * string = strdup(str);
    cconf_validator_t * v = calloc(1, sizeof(cconf_validator_t));
    char ** list = str_to_list(string, ",");
            
    asprintf(&v->docstring, "string, from set=[%s]", string);
    
    v->arg_free_fcn = (void (*)(void *)) strlist_free;
    v->arg = list;
       
    v->validate_fcn = &_validate_strlist;
    free(string);
    
    return v;
}

cconf_validator_t * cconf_validator_new_strset(char * set)
{
	char * string = strdup(set);
    cconf_validator_t * v = calloc(1, sizeof(cconf_validator_t));
    char ** list = str_to_list(string, ",");
            
    asprintf(&v->docstring, "string, unique subet of set=[%s]", string);
    
    v->arg_free_fcn = (void (*)(void *)) strlist_free;
    v->arg = list;
       
    v->validate_fcn = &_validate_strlist;
    free(string);
    
    return v;
	
}


cconf_validator_t * cconf_validator_new_string()
{
    cconf_validator_t * v = calloc(1, sizeof(cconf_validator_t));
    v->validate_fcn = &_validate_string;
    v->arg = NULL;
    v->docstring = strdup("string");
    
    return v;
}

cconf_validator_t * cconf_validator_new_user(cconf_validate_fcn_t fcn, void * arg, char * docstring)
{
    cconf_validator_t * v = calloc(1, sizeof(cconf_validator_t));
    v->validate_fcn = fcn;
    v->arg = arg;
    v->docstring = strdup(docstring);
    
    return v;
}

int cconf_validator_validate(cconf_validator_t * v, char ** err, char * value)
{
	char * e = NULL;
	int ret = v->validate_fcn(&e, value, v->arg);
	
	if (e && err)
		*err = e;
		
	return ret;	
}

void cconf_validator_free(cconf_validator_t * v)
{
	if (!v)
		return;
		
	if (v->arg_free_fcn)
		v->arg_free_fcn(v->arg);
		
    free(v->docstring);
    free(v);
}
